using System.Collections.Generic;
using System.Linq;

using UnityEngine;

using Gamelogic.Extensions;

public class LineMeshBuilderEx : MeshBuilder
{
    public List<Vector3> points;
    public bool loop;

    public float width = 1;
    public int cornerPointNum = 2;
    [Range(0, 1)]
    public float buffLenRate = 0.25f;
    [Range(0, 1)]
    public float innerCornerRadiusRate = 0.25f;
    [Range(0, 1)]
    public float outerCornerRadiusRate = 0.75f;

    Vector3 QuadraticBezier(float t, Vector3 P0, Vector3 P1, Vector3 P2)
    {
        float u = 1f - t;
        float tt = t * t;
        float uu = u * u;

        Vector3 p = uu * P0;  // (1 - t)^2 * P0
        p += 2 * u * t * P1;  // 2 * (1 - t) * t * P1
        p += tt * P2;         // t^2 * P2

        return p;
    }

    override protected List<Vector3> CalculateVertices()
    {
        if (points.Count <= 0) return null;

        float halfWidth = width / 2f;

        var topLines = new List<Line>();
        var bottomLines = new List<Line>();

        int segmentCount = loop ? points.Count : (points.Count - 1);


        for (int i = 0; i < segmentCount; i++)
        {
            int j = (i == points.Count - 1) ? 0 : i + 1;

            var direction = (points[j] - points[i]).normalized;

            meshDebug.AddArrow(points[i], direction, GLColor.Green);

            var left = direction.PerpXY();
            var top = points[i] + halfWidth * left;
            var bottom = points[i] - halfWidth * left;

            topLines.Add(new Line { offset = top, direction = direction });
            bottomLines.Add(new Line { offset = bottom, direction = direction });
        }

        List<Vector3> topIntersections = new List<Vector3>();
        List<Vector3> bottomIntersections = new List<Vector3>();

        for (int i = 0; i < points.Count; i++)
        {
            int j = (i == 0) ? points.Count - 1 : i - 1;
            if ((i == 0) && !loop)
            {
                //vertices.Add2(topLines[i].offset, bottomLines[i].offset);

                topIntersections.Add(topLines[i].offset);
                bottomIntersections.Add(bottomLines[i].offset);
            }
            else if ((i == points.Count - 1) && !loop)
            {
                var direction = topLines[j].direction;

                var left = direction.PerpXY();
                var top = points[i] + halfWidth * left;
                var bottom = points[i] - halfWidth * left;

                topIntersections.Add(top);
                bottomIntersections.Add(bottom);
            }
            else if (MeshBuilderUtils.IsParallel(topLines[i], topLines[j]))
            {
                topIntersections.Add(topLines[i].offset);
                bottomIntersections.Add(bottomLines[i].offset);
            }
            else
            {
                topIntersections.Add(MeshBuilderUtils.GetIntersection(topLines[i], topLines[j]));
                bottomIntersections.Add(MeshBuilderUtils.GetIntersection(bottomLines[i], bottomLines[j]));
            }
        }


        List<List<Vector3>> topCurvePoints = new();
        List<List<Vector3>> bottomCurvePoints = new();

        for (int i = 0; i < points.Count; i++)
        {
            int j = (i == 0) ? points.Count - 1 : i - 1;
            int k = (i == points.Count - 1) ? 0 : i + 1;

            while (topCurvePoints.Count <= i)
            {
                topCurvePoints.Add(new List<Vector3>());
                bottomCurvePoints.Add(new List<Vector3>());
            }

            if ((i == 0) && !loop)
            {
                //vertices.Add2(topLines[i].offset, bottomLines[i].offset);

                topCurvePoints[i].Add(topLines[i].offset);
                bottomCurvePoints[i].Add(bottomLines[i].offset);
            }
            else if ((i == points.Count - 1) && !loop)
            {
                var direction = topLines[j].direction;

                var left = direction.PerpXY();
                var top = points[i] + halfWidth * left;
                var bottom = points[i] - halfWidth * left;

                topCurvePoints[i].Add(top);
                bottomCurvePoints[i].Add(bottom);

                //vertices.Add2(top, bottom);
            }
            else if (MeshBuilderUtils.IsParallel(topLines[i], topLines[j]))
            {
                //Debug.Log("Parallel");

                topCurvePoints[i].Add(topLines[i].offset);
                bottomCurvePoints[i].Add(bottomLines[i].offset);

                //vertices.Add2(topLines[i].offset, bottomLines[i].offset);
            }
            else
            {

                var topIntersection = topIntersections[i];
                //MeshBuilderUtils.GetIntersection(topLines[i], topLines[j]);
                var bottomIntersection = bottomIntersections[i];
                //MeshBuilderUtils.GetIntersection(bottomLines[i], bottomLines[j]);

                if (cornerPointNum > 1)
                {
                    var lineAngle = Vector3.SignedAngle(topLines[j].direction, topLines[i].direction, Vector3.forward);

                    var a = width / 2;
                    var b = Vector3.Distance(topIntersection, bottomIntersection) / 2;
                    var c = Mathf.Sqrt(b * b - a * a);


#if true
                    var innerCornerOffset = innerCornerRadiusRate * width;
                    var innerCornerRadius = (innerCornerOffset / c) * a;
                    var innerB = Mathf.Sqrt(innerCornerOffset * innerCornerOffset + innerCornerRadius * innerCornerRadius);

                    var outerCornerOffset = Mathf.Sqrt(Mathf.Pow(b * 2 + innerB, 2) - Mathf.Pow(innerCornerRadius + width, 2));
#else
                            var outerCornerOffset = c;
#endif

                    //Debug.LogError($"test {outerCornerOffset} {innerCornerRadius} {c} ");

                    var topCornerRadius = innerCornerOffset;
                    var bottomCornerRadius = outerCornerOffset * outerCornerRadiusRate;


                    if (lineAngle < 0)
                    {
                        topCornerRadius = outerCornerOffset * outerCornerRadiusRate;
                        bottomCornerRadius = innerCornerOffset;
                    }

                    //{
                    //    Debug.LogError($"test {i} {j} {topLines.Count} {topIntersections.Count}");
                    //}

                    var top0 = topIntersection - topLines[j].direction * Mathf.Min(Vector3.Distance(topIntersections[j], topIntersections[i]) / 2, topCornerRadius);
                    var top1 = topIntersection + topLines[i].direction * Mathf.Min(Vector3.Distance(topIntersections[k], topIntersections[i]) / 2, topCornerRadius);



                    //var perpTopLine0 = new Line() { offset = top0, direction = topLines[j].direction.PerpXY() };
                    //var perpTopLine1 = new Line() { offset = top1, direction = topLines[i].direction.PerpXY() };
                    //var topCenter = MeshBuilderUtils.GetIntersection(perpTopLine0, perpTopLine1);
                    //var topV0 = top0 - topCenter;
                    //var topV1 = top1 - topCenter;

                    var bottom0 = bottomIntersection - bottomLines[j].direction * Mathf.Min(Vector3.Distance(bottomIntersections[j], bottomIntersections[i]) / 2, bottomCornerRadius);
                    var bottom1 = bottomIntersection + bottomLines[i].direction * Mathf.Min(Vector3.Distance(bottomIntersections[k], bottomIntersections[i]) / 2, bottomCornerRadius);


                    //var perpBottomLine0 = new Line() { offset = bottom0, direction = bottomLines[j].direction.PerpXY() };
                    //var perpBottomLine1 = new Line() { offset = bottom1, direction = bottomLines[i].direction.PerpXY() };
                    //var bottomCenter = MeshBuilderUtils.GetIntersection(perpBottomLine0, perpBottomLine1);
                    //var bottomV0 = bottom0 - bottomCenter;
                    //var bottomV1 = bottom1 - bottomCenter;


                    //meshDebug.AddDotXY(topCenter, GLColor.Magenta);

                    //meshDebug.AddDotXY(bottomCenter, GLColor.Magenta);


                    for (int s = 0; s <= cornerPointNum - 1; s++)
                    {
                        var r = s / (float)(cornerPointNum - 1);
                        var top = QuadraticBezier(r, top0, topIntersection, top1);
                        var bottom = QuadraticBezier(r, bottom0, bottomIntersection, bottom1);


                        topCurvePoints[i].Add(top);
                        bottomCurvePoints[i].Add(bottom);
                    }

                    //for (int s = 0; s <= cornerPointNum - 1; s++)
                    //{
                    //    var r = s / (float)(cornerPointNum - 1);
                    //    var top = Vector3.Slerp(topV0, topV1, r) + topCenter;
                    //    var bottom = Vector3.Slerp(bottomV0, bottomV1, r) + bottomCenter;


                    //    topCurvePoints[i].Add(top);
                    //    bottomCurvePoints[i].Add(bottom);
                    //}


                }
                //else
                //{
                //    vertices.Add2(topIntersection, bottomIntersection);
                //}
            }

        }

        var vertices = new List<Vector3>();

        for (int i = 0; i < points.Count; i++)
        {
            int j = (i == 0) ? points.Count - 1 : i - 1;
            int k = (i == points.Count - 1) ? 0 : i + 1;

            if ((i == 0) && !loop)
            {
                vertices.Add2(topLines[i].offset, bottomLines[i].offset);
            }
            else if ((i == points.Count - 1) && !loop)
            {
                var direction = topLines[j].direction;

                var left = direction.PerpXY();
                var top = points[i] + halfWidth * left;
                var bottom = points[i] - halfWidth * left;

                vertices.Add2(top, bottom);
            }
            else if (MeshBuilderUtils.IsParallel(topLines[i], topLines[j]))
            {
                //Debug.Log("Parallel");

                vertices.Add2(topLines[i].offset, bottomLines[i].offset);
            }
            else
            {
                var topIntersection = topIntersections[i];
                var bottomIntersection = bottomIntersections[i];
                //var topIntersection = MeshBuilderUtils.GetIntersection(topLines[i], topLines[j]);
                //var bottomIntersection = MeshBuilderUtils.GetIntersection(bottomLines[i], bottomLines[j]);

                if (cornerPointNum > 1)
                {
                    //                var lineAngle = Vector3.SignedAngle(topLines[j].direction, topLines[i].direction, Vector3.forward);

                    //                var a = width / 2;
                    //                var b = Vector3.Distance(topIntersection, bottomIntersection) / 2;
                    //                var c = Mathf.Sqrt(b * b - a * a);


                    //#if true
                    //                    var innerCornerOffset = innerCornerRadiusRate * width;
                    //                    var innerCornerRadius = (innerCornerOffset / c) * a;
                    //                    var innerB = Mathf.Sqrt(innerCornerOffset * innerCornerOffset + innerCornerRadius * innerCornerRadius);

                    //                    var outerCornerOffset = Mathf.Sqrt(Mathf.Pow(b * 2 + innerB, 2) - Mathf.Pow(innerCornerRadius + width, 2));
                    //#else
                    //                    var outerCornerOffset = c;
                    //#endif

                    //Debug.LogError($"test {outerCornerOffset} {innerCornerRadius} {c} ");

                    //    var topCornerRadius = innerCornerOffset;
                    //var bottomCornerRadius = outerCornerOffset * outerCornerRadiusRate;


                    //if (lineAngle < 0)
                    //{
                    //        topCornerRadius = outerCornerOffset * outerCornerRadiusRate;
                    //        bottomCornerRadius = innerCornerOffset;
                    //}

                    var curTopBegin = topCurvePoints[i][0];
                    var curTopEnd = topCurvePoints[i][topCurvePoints[i].Count - 1];


                    //var perpTopLine0 = new Line() { offset = top0, direction = topLines[j].direction.PerpXY() };
                    //var perpTopLine1 = new Line() { offset = top1, direction = topLines[i].direction.PerpXY() };
                    //var topCenter = MeshBuilderUtils.GetIntersection(perpTopLine0, perpTopLine1);
                    //var topV0 = top0 - topCenter;
                    //var topV1 = top1 - topCenter;

                    var curBottomBegin = bottomCurvePoints[i][0];
                    var curBottomEnd = bottomCurvePoints[i][bottomCurvePoints[i].Count - 1];


                    //var perpBottomLine0 = new Line() { offset = bottom0, direction = bottomLines[j].direction.PerpXY() };
                    //var perpBottomLine1 = new Line() { offset = bottom1, direction = bottomLines[i].direction.PerpXY() };
                    //var bottomCenter = MeshBuilderUtils.GetIntersection(perpBottomLine0, perpBottomLine1);
                    //var bottomV0 = bottom0 - bottomCenter;
                    //var bottomV1 = bottom1 - bottomCenter;

                    var buffLen = buffLenRate * width;
                    if (buffLen > 0)
                    {
                        var lastTopEnd = topCurvePoints[j][topCurvePoints[j].Count - 1];
                        var lastBottomEnd = bottomCurvePoints[j][bottomCurvePoints[j].Count - 1];

                        var topDist0 = Vector3.Distance(topLines[j].offset, curTopBegin);
                        var bottomDist0 = Vector3.Distance(bottomLines[j].offset, curBottomBegin);
                        var minDist0 = Mathf.Min(topDist0, bottomDist0) - buffLen;
                        var p0 = topLines[j].Evaluate(Mathf.Max(minDist0, (Vector3.Distance(topLines[j].offset, curTopBegin) - Vector3.Distance(lastTopEnd, topLines[j].offset)) / 2 + Vector3.Distance(topLines[j].offset, lastTopEnd)));
                        var p1 = bottomLines[j].Evaluate(Mathf.Max(minDist0, (Vector3.Distance(bottomLines[j].offset, curBottomBegin) - Vector3.Distance(lastBottomEnd, bottomLines[j].offset)) / 2 + Vector3.Distance(bottomLines[j].offset, lastBottomEnd)));
                        //var p0 = topLines[j].Evaluate(minDist0);
                        //var p1 = bottomLines[j].Evaluate(minDist0);

                        for (int t = 0; t < cornerPointNum; t++)
                        {
                            var lerpP0 = Vector3.Lerp(p0, curTopBegin, t / (float)cornerPointNum);
                            var lerpP1 = Vector3.Lerp(p1, curBottomBegin, t / (float)cornerPointNum);

                            meshDebug.AddDotXY(lerpP0, GLColor.Magenta);
                            meshDebug.AddDotXY(lerpP1, GLColor.Green);
                            vertices.Add2(lerpP0, lerpP1);
                        }
                    }



                    for (int u = 0; u < topCurvePoints[i].Count; u++)
                    {
                        var top = topCurvePoints[i][u];
                        var bottom = bottomCurvePoints[i][u];
                        meshDebug.AddDotXY(top, GLColor.Magenta);
                        meshDebug.AddDotXY(bottom, GLColor.Green);
                        vertices.Add2(top, bottom);
                    }

                    //for (int s = 0; s <= cornerPointNum - 1; s++)
                    //{
                    //    var r = s / (float)(cornerPointNum - 1);
                    //    var top = Vector3.Slerp(topV0, topV1, r) + topCenter;
                    //    var bottom = Vector3.Slerp(bottomV0, bottomV1, r) + bottomCenter;

                    //    meshDebug.AddDotXY(top, GLColor.Magenta);
                    //    meshDebug.AddDotXY(bottom, GLColor.Green);
                    //    vertices.Add2(top, bottom);
                    //}

                    if (buffLen > 0)
                    {
                        var nextTopStart = topCurvePoints[k][0];
                        var nextBottomStart = bottomCurvePoints[k][0];

                        var topDist0 = Vector3.Distance(topLines[i].offset, curTopEnd);
                        var bottomDist0 = Vector3.Distance(bottomLines[i].offset, curBottomEnd);
                        var maxDist0 = Mathf.Max(topDist0, bottomDist0) + buffLen;
                        var p0 = topLines[i].Evaluate(Mathf.Min(maxDist0, (Vector3.Distance(nextTopStart, topLines[i].offset) - Vector3.Distance(topLines[i].offset, curTopEnd)) / 2 + Vector3.Distance(topLines[i].offset, curTopEnd)));
                        var p1 = bottomLines[i].Evaluate(Mathf.Min(maxDist0, (Vector3.Distance(nextBottomStart, bottomLines[i].offset) - Vector3.Distance(bottomLines[i].offset, curBottomEnd)) / 2 + Vector3.Distance(bottomLines[i].offset, curBottomEnd)));
                        //var p0 = topLines[i].Evaluate(maxDist0);
                        //var p1 = bottomLines[i].Evaluate(maxDist0);



                        //var topDist0 = Vector3.Distance(topLines[i].offset, top1);
                        //var bottomDist0 = Vector3.Distance(bottomLines[i].offset, bottom1);
                        //var maxDist0 = Mathf.Max(topDist0, bottomDist0);
                        //var p0 = topLines[i].Evaluate(maxDist0 + buffLen);
                        //var p1 = bottomLines[i].Evaluate(maxDist0 + buffLen);

                        for (int t = 1; t < cornerPointNum + 1; t++)
                        {
                            var lerpP0 = Vector3.Lerp(curTopEnd, p0, t / (float)cornerPointNum);
                            var lerpP1 = Vector3.Lerp(curBottomEnd, p1, t / (float)cornerPointNum);

                            meshDebug.AddDotXY(lerpP0, GLColor.Magenta);
                            meshDebug.AddDotXY(lerpP1, GLColor.Green);
                            vertices.Add2(lerpP0, lerpP1);
                        }
                    }
                }
                else
                {
                    vertices.Add2(topIntersection, bottomIntersection);
                }
            }

        }


        if (loop)
        {
            vertices.Add2(vertices[0], vertices[1]);
        }

        return vertices;
    }

    override protected List<int> CalculateTriangles()
    {
        int newPointCount = GetVertices().Count / 2;
        int segmentCount = newPointCount - 1;
        var triangles = new List<int>();

        for (int i = 0; i < segmentCount; i++)
        {
            int j = (i == newPointCount - 1) ? 0 : i + 1;

            triangles.AddRange(
               MeshBuilderUtils.QuadTriangles(
                  2 * i + 0,
                  2 * i + 1,
                  2 * j + 1,
                  2 * j + 0
               )
            );
        }

        return triangles;
    }

    override protected List<Vector2> CalculateUvs(List<Vector3> vertices)
    {
        var newPoints = new List<Vector3>();
        for (int i = 0; i < vertices.Count; i += 2)
        {
            newPoints.Add((vertices[i + 1] + vertices[i]) / 2);
        }

        var lengths = newPoints.Differences((u, v) => (v - u).magnitude, loop);
        float totalLength = lengths.Sum();

        int segmentCount = newPoints.Count - 1;

        var uvs = new List<Vector2>();
        uvs.Add2(Vector2.zero, Vector2.up);

        float accumulativeLength = 0;

        for (int i = 0; i < newPoints.Count - 1; i++)
        {
            accumulativeLength += lengths[i];
            float u = accumulativeLength / totalLength;

            uvs.Add2(new Vector2(u, 0), new Vector2(u, 1));
        }

        return uvs;
    }
}